Trajectory Divergence

from IPython.display import display
import math
import random
import pickle as pkl
import numpy as np
import pandas as pd

import plotly.graph_objects as go
import plotly.express as px
import plotly.offline as pyo
pyo.init_notebook_mode()
from ipythonblocks import BlockGrid
from webcolors import name_to_rgb

Trajectory data is saved as a pandas dataframe in a .pkl file. We can also calculate mean trajectories for each clip and add it to the dataframe. Let’s import it and display the dataframe and its clip properties.

with open('trajectories.pkl', 'rb') as f:
    data = pkl.load(f)
traj_df = data['traj_df'] # pandas dataframe with 3D coordinates, time, particpant id (pid), clip id (clip) and  clip name as columns
clip_len = data['clip_len'] # array consisting number of time points indexed by clip_id
    
# calculate mean trajectories per clip
traj_df[['mean_x', 'mean_y', 'mean_z']] = traj_df.drop(columns='pid').groupby(['clip', 'time']).transform('mean')

# display df
display(traj_df)

# print unique clip names and lengths
print('Clip ID   Clip Name     Clip Length')
unique_clip_names = traj_df.clip_name.unique()
for i in range(len(clip_len)):
    print(f'{str(i):10}{unique_clip_names[i]:14}{clip_len[i]}')
x y z time pid clip clip_name mean_x mean_y mean_z
0 -0.068375 0.292656 0.076036 0 1 0 testretest -0.535419 -0.584089 0.527666
1 -0.560828 0.290854 0.095379 1 1 0 testretest -0.828858 -1.073595 0.652328
2 0.248541 -0.024260 -0.019393 2 1 0 testretest -0.948996 -1.365094 0.686838
3 -0.021169 0.253559 1.618106 3 1 0 testretest -1.016553 -1.504698 0.718371
4 -0.218407 0.420255 2.193483 4 1 0 testretest -1.068053 -1.757247 0.923804
... ... ... ... ... ... ... ... ... ... ...
245399 -6.074567 -14.429848 13.255661 251 76 14 starwars 7.020762 -20.638204 2.571888
245400 -5.333982 -15.487228 15.741982 252 76 14 starwars 6.981013 -20.744694 2.663826
245401 -5.229411 -14.917367 16.472929 253 76 14 starwars 7.065092 -20.584518 2.731824
245402 -4.298551 -12.905822 15.564515 254 76 14 starwars 7.200529 -20.280288 2.620943
245403 -3.862932 -14.278425 16.305193 255 76 14 starwars 7.261687 -20.207359 2.578671

245404 rows × 10 columns

Clip ID   Clip Name     Clip Length
0         testretest    84
1         twomen        245
2         bridgeville   222
3         pockets       189
4         overcome      65
5         inception     227
6         socialnet     260
7         oceans        250
8         flower        181
9         hotel         186
10        garden        205
11        dreary        143
12        homealone     233
13        brokovich     231
14        starwars      256

Let’s create a colorscale so we can easily visualize the 15 clips on a graph.

grid = BlockGrid(15,1,fill=(0,0,0))
grid.block_size = 50
grid.lines_on = False

colors = ['slategray','sienna','darkred','crimson','darkorange','darkgoldenrod','darkkhaki','mediumseagreen','darkgreen','darkcyan','cornflowerblue','mediumblue','blueviolet','purple','hotpink']
i = 0
for block in grid:
    color = name_to_rgb(colors[i])
    block.set_colors(color[0],color[1],color[2])
    i+=1
    
grid

Let’s plot the mean trajectory for each clip, averaged across all participants.

plotly_data = []

for clip, clip_name in enumerate(traj_df.clip_name.unique()):

    # mean trajectories
    temp_df = traj_df[(traj_df.clip_name==clip_name) & (traj_df.pid==1)]
    mean_traj = go.Scatter3d(
        x=temp_df['mean_x'],
        y=temp_df['mean_y'],
        z=temp_df['mean_z'],
        customdata=temp_df['time'],
        mode='markers+lines',
        marker={'size':2, 'color': colors[clip]},
        line={'width':4, 'color': colors[clip]},
        name=clip_name,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}')
    plotly_data.append(mean_traj)

# formatting
plotly_layout = go.Layout(showlegend=True, 
                          autosize=False,
                          width=800, 
                          height=800,
                          margin={'l':0, 'r':0, 't':40, 'b':0},
                          legend={'orientation':'h',
                                  'itemsizing':'constant',
                                  'xanchor':'center',
                                  'yanchor':'bottom',
                                  'y':-0.1,
                                  'x':0.5},
                          title={'text':'Mean Trajectory Per Clip',
                                 'xanchor':'center',
                                 'yanchor':'top',
                                 'x':0.5,
                                 'y':0.98})

plotly_config = {'displaylogo':False,
                 'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}

fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)

Now let’s look at two clips that have similar mean trajectories, “ocean” and “brokovich” in this case. We’ll plot their mean trajectories (represented as points connected by lines) as well as all of their individual trajectories (represented as individual unconnected points).

plotly_data = []



# OCEANS

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'blue'},
    line={'width':4, 'color':'blue'},
    name='oceans',
    legendgroup='oceans',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}')
plotly_data.append(mean_traj)

## individual trajectories
temp_df = traj_df[(traj_df.clip_name=='oceans')]
pid_traj = go.Scatter3d(
    x=temp_df['x'],
    y=temp_df['y'],
    z=temp_df['z'],
    customdata=temp_df['time'],
    mode='markers',
    marker={'size':0.5, 'color':'blue'},
    opacity=0.5,
    name='oceans',
    legendgroup='oceans',
    showlegend=False,
    hoverinfo='skip')
plotly_data.append(pid_traj)



# BROKOVICH

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'red'},
    line={'width':4, 'color':'red'},
    name='brokovich',
    legendgroup='brokovich',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}')
plotly_data.append(mean_traj)

## individual trajectories
temp_df = traj_df[(traj_df.clip_name=='brokovich')]
pid_traj = go.Scatter3d(
    x=temp_df['x'],
    y=temp_df['y'],
    z=temp_df['z'],
    customdata=temp_df['time'],
    mode='markers',
    marker={'size':0.5, 'color':'red'},
    opacity=0.5,
    name='brokovich',
    legendgroup='brokovich',
    showlegend=False,
    hoverinfo='skip')
plotly_data.append(pid_traj)



# formatting
plotly_layout = go.Layout(showlegend=True, 
                          autosize=False,
                          width=800, 
                          height=600,
                          margin={'l':0, 'r':0, 't':35, 'b':0},
                          legend={'orientation':'h',
                                  'itemsizing':'constant',
                                  'xanchor':'center',
                                  'yanchor':'bottom',
                                  'y':-0.055,
                                  'x':0.5},
                          title={'text':'Mean and Individual Trajectories for \"oceans\" and \"brokovich\"',
                                 'xanchor':'center',
                                 'yanchor':'top',
                                 'x':0.5,
                                 'y':0.98})

plotly_config = {'displaylogo':False,
                 'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}

fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)

We see that the majority of individual points lie in clouds near the end of their corresponding mean trajectories, with many other points surrounding the mean trajectories. This behavior indicates that most individual trajectories follow a similar path and end in a similar location, especially since the mean trajectories themselves stay in a relatively close clump for the latter half of the clips.

However, there are a fair amount of points that don’t follow this trend. Let’s plot the endpoints of all individual trajectories to see where they finish at the end of the clip.

plotly_data = []



# OCEANS

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'blue'},
    line={'width':4, 'color':'blue'},
    name='oceans',
    legendgroup='oceans',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}<br>mean')
plotly_data.append(mean_traj)

temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='oceans')[0][0]]-1)]
pid_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df[['time','pid']],
    mode='markers',
    marker={'size':8, 'color':'blue'},
    opacity=0.5,
    name='oceans',
    legendgroup='oceans',
    showlegend=False,
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
plotly_data.append(pid_traj)

## individual trajectories
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='oceans')[0][0]]-1)]
pid_traj = go.Scatter3d(
    x=temp_df['x'],
    y=temp_df['y'],
    z=temp_df['z'],
    customdata=temp_df[['time','pid']],
    mode='markers',
    marker={'size':4, 'color':'blue'},
    opacity=0.5,
    name='oceans',
    legendgroup='oceans',
    showlegend=False,
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
plotly_data.append(pid_traj)



# BROKOVICH

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'red'},
    line={'width':4, 'color':'red'},
    name='brokovich',
    legendgroup='brokovich',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}<br>mean')
plotly_data.append(mean_traj)

temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='brokovich')[0][0]]-1)]
pid_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df[['time','pid']],
    mode='markers',
    marker={'size':8, 'color':'red'},
    opacity=0.5,
    name='brokovich',
    legendgroup='brokovich',
    showlegend=False,
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
plotly_data.append(pid_traj)

## individual trajectories
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='brokovich')[0][0]]-1)]
pid_traj = go.Scatter3d(
    x=temp_df['x'],
    y=temp_df['y'],
    z=temp_df['z'],
    customdata=temp_df[['time','pid']],
    mode='markers',
    marker={'size':4, 'color':'red'},
    opacity=0.5,
    name='brokovich',
    legendgroup='brokovich',
    showlegend=False,
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
plotly_data.append(pid_traj)



# formatting
plotly_layout = go.Layout(showlegend=True, 
                          autosize=False,
                          width=800, 
                          height=600,
                          margin={'l':0, 'r':0, 't':35, 'b':0},
                          legend={'orientation':'h',
                                  'itemsizing':'constant',
                                  'xanchor':'center',
                                  'yanchor':'bottom',
                                  'y':-0.055,
                                  'x':0.5},
                          title={'text':'Individual Trajectory Endpoints for \"oceans\" and \"brokovich\"',
                                 'xanchor':'center',
                                 'yanchor':'top',
                                 'x':0.5,
                                 'y':0.98})

plotly_config = {'displaylogo':False,
                 'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}

fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)

It appears that most trajectories end at a similar location as the mean trajectories. There aren’t any other noticeable groups, so let’s take a look at some of the trajectories that fall into the cloud around the mean trajectories.

plotly_data = []



# OCEANS

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'blue'},
    line={'width':4, 'color':'blue'},
    name='oceans',
    legendgroup='oceans',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}<br>mean')
plotly_data.append(mean_traj)

## individual trajectories
for pid in ([32,57,73]):
    temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==pid)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers+lines',
        marker={'size':1, 'color':'blue'},
        opacity=0.5,
        name='oceans',
        legendgroup='oceans',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)
    
    temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==pid) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='oceans')[0][0]]-1)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers',
        marker={'size':8, 'color':'blue'},
        opacity=0.7,
        name='oceans',
        legendgroup='oceans',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)



# BROKOVICH

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'red'},
    line={'width':4, 'color':'red'},
    name='brokovich',
    legendgroup='brokovich',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}<br>mean')
plotly_data.append(mean_traj)

## individual trajectories
for pid in ([2,20,72]):
    temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==pid)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers+lines',
        marker={'size':1, 'color':'red'},
        opacity=0.5,
        name='brokovich',
        legendgroup='brokovich',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)
    
    temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==pid) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='brokovich')[0][0]]-1)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers',
        marker={'size':8, 'color':'red'},
        opacity=0.7,
        name='brokovich',
        legendgroup='brokovich',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)
    



# formatting
plotly_layout = go.Layout(showlegend=True, 
                          autosize=False,
                          width=800, 
                          height=600,
                          margin={'l':0, 'r':0, 't':35, 'b':0},
                          legend={'orientation':'h',
                                  'itemsizing':'constant',
                                  'xanchor':'center',
                                  'yanchor':'bottom',
                                  'y':-0.055,
                                  'x':0.5},
                          title={'text':'Mean-Like Trajectories for \"oceans\" and \"brokovich\"',
                                 'xanchor':'center',
                                 'yanchor':'top',
                                 'x':0.5,
                                 'y':0.98})

plotly_config = {'displaylogo':False,
                 'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}

fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)

Just from looking at a few individual trajectories, we can notice that some trajectories do meander a fair amount before ending at the mean trajectory cloud. Notable examples include oceans 32, oceans 57, brokovich 20, and brokovich 72. Some other trajectories such as oceans 73 and brokovich 2 do follow a more similar path to the mean trajectory, but they still exhibit slight meandering.

We should also take a look at some trajectories that ended at a completely different point from the mean trajectories.

plotly_data = []



# OCEANS

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'blue'},
    line={'width':4, 'color':'blue'},
    name='oceans',
    legendgroup='oceans',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}<br>mean')
plotly_data.append(mean_traj)

temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==1) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='oceans')[0][0]]-1)]
pid_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df[['time','pid']],
    mode='markers',
    marker={'size':8, 'color':'blue'},
    opacity=0.5,
    name='oceans',
    legendgroup='oceans',
    showlegend=False,
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
plotly_data.append(pid_traj)

## individual trajectories
for pid in ([17,18]):
    temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==pid)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers+lines',
        marker={'size':1, 'color':'blue'},
        opacity=0.5,
        name='oceans',
        legendgroup='oceans',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)
    
    temp_df = traj_df[(traj_df.clip_name=='oceans') & (traj_df.pid==pid) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='oceans')[0][0]]-1)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers',
        marker={'size':8, 'color':'blue'},
        opacity=0.5,
        name='oceans',
        legendgroup='oceans',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)



# BROKOVICH

## mean trajectory 
temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1)]
mean_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df['time'],
    mode='markers+lines',
    marker={'size':2, 'color':'red'},
    line={'width':4, 'color':'red'},
    name='brokovich',
    legendgroup='brokovich',
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata}<br>mean')
plotly_data.append(mean_traj)

temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==1) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='brokovich')[0][0]]-1)]
pid_traj = go.Scatter3d(
    x=temp_df['mean_x'],
    y=temp_df['mean_y'],
    z=temp_df['mean_z'],
    customdata=temp_df[['time','pid']],
    mode='markers',
    marker={'size':8, 'color':'red'},
    opacity=0.5,
    name='brokovich',
    legendgroup='brokovich',
    showlegend=False,
    hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
plotly_data.append(pid_traj)

## individual trajectories
for pid in ([49,56]):
    temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==pid)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers+lines',
        marker={'size':1, 'color':'red'},
        opacity=0.5,
        name='brokovich',
        legendgroup='brokovich',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)
    
    temp_df = traj_df[(traj_df.clip_name=='brokovich') & (traj_df.pid==pid) & (traj_df.time==clip_len[np.where(traj_df.clip_name.unique()=='brokovich')[0][0]]-1)]
    pid_traj = go.Scatter3d(
        x=temp_df['x'],
        y=temp_df['y'],
        z=temp_df['z'],
        customdata=temp_df[['time','pid']],
        mode='markers',
        marker={'size':8, 'color':'red'},
        opacity=0.5,
        name='brokovich',
        legendgroup='brokovich',
        showlegend=False,
        hovertemplate='x: %{x:.3f}<br>y: %{y:.3f}<br>z: %{z:.3f}<br>t: %{customdata[0]}<br>pid: %{customdata[1]}')
    plotly_data.append(pid_traj)
    



# formatting
plotly_layout = go.Layout(showlegend=True, 
                          autosize=False,
                          width=800, 
                          height=600,
                          margin={'l':0, 'r':0, 't':35, 'b':0},
                          legend={'orientation':'h',
                                  'itemsizing':'constant',
                                  'xanchor':'center',
                                  'yanchor':'bottom',
                                  'y':-0.055,
                                  'x':0.5},
                          title={'text':'Mean-Unlike Trajectories for \"oceans\" and \"brokovich\"',
                                 'xanchor':'center',
                                 'yanchor':'top',
                                 'x':0.5,
                                 'y':0.98})

plotly_config = {'displaylogo':False,
                 'modeBarButtonsToRemove': ['resetCameraLastSave3d','orbitRotation']}

fig_traj = go.Figure(data=plotly_data, layout=plotly_layout)
fig_traj.show(config=plotly_config)

These trajectories are completely different from the mean trajectories. They begin at an entirely different direction and continue to travel in a fashion that is seemingly unrelated to their respective mean trajectories. However, it is important to remember that these mean-unlike trajectories are few in quantity in comparison to mean-unlike trajectories.

Divergence of a Single Trajectory

Total Divergence

# compares adjacent distances
def divergence_v1(traj):
    length = traj.shape[0]
    total_dist = 0
    for i in range(1,length):
        total_dist += math.sqrt((traj[i,0]-traj[i-1,0])**2 + (traj[i,1]-traj[i-1,1])**2 + (traj[i,2]-traj[i-1,2])**2)
    
    inc = 3 # increment = number of time steps to move forward
    dist = np.zeros(length-inc)
    for i in range(inc,length):
        dist[i-inc] = math.sqrt((traj[i,0]-traj[i-inc,0])**2 + (traj[i,1]-traj[i-inc,1])**2 + (traj[i,2]-traj[i-inc,2])**2)
    
    total_div = 0
    for start in range(inc,2*inc):
        div = 0
        for i in range(start, length-inc, inc):
            div += abs(dist[i] - dist[i-inc])
        total_div += div
    
    return(total_div / (inc*total_dist))



# compares all distances
def divergence_v2(traj):
    length = traj.shape[0]
    total_dist = 0
    for i in range(1,length):
        total_dist += math.sqrt((traj[i,0]-traj[i-1,0])**2 + (traj[i,1]-traj[i-1,1])**2 + (traj[i,2]-traj[i-1,2])**2)
    
    inc = 3 # increment = number of time steps to move forward
    dist = np.zeros(length-inc)
    for i in range(inc,length):
        dist[i-inc] = math.sqrt((traj[i,0]-traj[i-inc,0])**2 + (traj[i,1]-traj[i-inc,1])**2 + (traj[i,2]-traj[i-inc,2])**2)
    
    total_div = 0
    for i in range(len(dist)-1):
        for j in range(i+1,len(dist)):
            total_div += abs(dist[i]-dist[j])
    
    return(total_div / (inc*total_dist))

Now let’s make sure our definition of divergence makes sense for some defined example trajectories.

Work in progress (currently priority #2)

def test_divergence(name):
    if (name=='line_constant'): # expected divergence = 0
        length = random.randint(50,100)
        traj_arr = np.zeros((length,3))
        segment_len = random.randint(0,10)
        direction = np.random.rand(3)
        for i in range(1,length):
            for j in range(len(direction)):
                traj_arr[i,j] = traj_arr[i-1,j] + segment_len*direction[j]
                
    elif (name=='line_random'): # expected divergence = 0
        length = random.randint(50,100)
        traj_arr = np.zeros((length,3))
        direction = np.random.rand(3)
        for i in range(1,length):
            segment_len = random.randint(0,10)
            for j in range(len(direction)):
                traj_arr[i,j] = traj_arr[i-1,j] + segment_len*direction[j]
                
    elif (name=='line_increasing'): # expected divergence = 0
        length = random.randint(50,100)
        traj_arr = np.zeros((length,3))
        direction = np.random.rand(3)
        segment_len = random.randint(0,10)
        for i in range(1,length):
            for j in range(len(direction)):
                traj_arr[i,j] = traj_arr[i-1,j] + segment_len*direction[j]
            segment_len = segment_len * 1.5
        
    elif (name=='semicircle'): # expected divergence = 0
        length = random.randint(50,100)
        traj_arr = np.zeros((length,3))
        radius = random.random()*50
        for i in range(length):
            traj_arr[i,0] = radius * math.cos(math.pi*i/length)
            traj_arr[i,1] = radius * math.sin(math.pi*i/length)
        
#     elif (name=='curve'):

#     elif (name=='zigzag'):
        
    return divergence_v1(traj_arr)
print(f"Divergence of line with constant velocity: {test_divergence('line_constant'):.3f}")
print(f"Divergence of line with random velocity: {test_divergence('line_random'):.3f}")
print(f"Divergence of line with increasing velocity: {test_divergence('line_increasing'):.3f}")
print(f"Divergence of semicicle: {test_divergence('semicircle'):.3f}")
Divergence of line with constant velocity: 0.000
Divergence of line with random velocity: 0.388
Divergence of line with increasing velocity: 0.495
Divergence of semicicle: 0.000
temp_df = traj_df[(traj_df.clip_name=='overcome') & (traj_df.pid==1)]
single_traj = np.array(temp_df[['x','y','z']])
print(f"Divergence of single trajectory: {divergence_v1(single_traj):.3f}")

mean_traj = np.array(temp_df[['mean_x','mean_y','mean_z']])
print(f"Divergence of mean trajectory: {divergence_v1(mean_traj):.3f}")
Divergence of single trajectory: 0.365
Divergence of mean trajectory: 0.290

Divergence as a Function of Time

Work in progress (currently priority #1)

Need to define a function and create a figure to show how it works. Then I can start testing on defined examples.

Divergence Between Two Trajectories

Work in progress (currently priority #3)

Need to define a function and create a figure to show how it works.